package compiler.imcode;

import java.util.*;

import compiler.abstree.tree.AbsBinExpr;
import compiler.abstree.tree.AbsTypeDecl;
import compiler.abstree.tree.AbsValName;
import compiler.abstree.tree.AbsVarDecl;
import compiler.abstree.AbsVisitor;
import compiler.abstree.tree.*;
import compiler.frames.*;
import compiler.semanal.SemDesc;
import compiler.semanal.type.*;

public class IMCodeGenerator implements AbsVisitor {

	public LinkedList<ImcChunk> chunks = new LinkedList<ImcChunk>();
	private HashMap<AbsTree, ImcCode> imc = new HashMap<AbsTree, ImcCode>();
	private Stack<FrmFrame> subprogStack = new Stack<FrmFrame>();
	private int level = 0;
	private SemArrayType arrayType;
	private AbsRecordType record;
	
	@Override
	public void visit(AbsAlloc acceptor) {
		ImcCALL alloc = new ImcCALL(FrmLabel.newLabel("malloc"));
		alloc.args.add(this.returnStaticLink(0));
		alloc.args.add(new ImcCONST(4));
		
		this.imc.put(acceptor, alloc);
	}

	@Override
	public void visit(AbsArrayType acceptor) {
		this.arrayType=(SemArrayType) SemDesc.getActualType(acceptor);
	}

	@Override
	public void visit(AbsAssignStmt acceptor) {
		acceptor.dstExpr.accept(this);
		acceptor.srcExpr.accept(this);
		
		ImcExpr dstExpr = (ImcExpr) this.imc.get(acceptor.dstExpr);
		ImcExpr srcExpr = (ImcExpr) this.imc.get(acceptor.srcExpr);
		
		ImcMOVE move = new ImcMOVE(dstExpr, srcExpr);
		this.imc.put(acceptor, move);
	}

	@Override
	public void visit(AbsAtomConst acceptor) {
		
		switch(acceptor.type) {
			case AbsAtomConst.BOOL:
				if(acceptor.value.equalsIgnoreCase("true"))
					this.imc.put(acceptor, new ImcCONST(1));
				else
					this.imc.put(acceptor, new ImcCONST(0));
				break;
			case AbsAtomConst.INT:
				this.imc.put(acceptor, new ImcCONST(Integer.parseInt(acceptor.value)));
				break;
			case AbsAtomConst.CHAR:
				this.imc.put(acceptor, new ImcCONST((int)acceptor.value.charAt(1)));
		}
	}

	@Override
	public void visit(AbsAtomType acceptor) {
	}

	@Override
	public void visit(AbsBinExpr acceptor) {
		acceptor.fstExpr.accept(this);
		acceptor.sndExpr.accept(this);
		
		ImcExpr fstCode=(ImcExpr) this.imc.get(acceptor.fstExpr);
		ImcExpr sndCode=(ImcExpr) this.imc.get(acceptor.sndExpr);
		
		switch(acceptor.oper) {
			case  AbsBinExpr.ADD:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.ADD, fstCode, sndCode)); break;
			case  AbsBinExpr.SUB:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.SUB, fstCode, sndCode)); break;
			case  AbsBinExpr.MUL:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.MUL, fstCode, sndCode)); break;
			case  AbsBinExpr.DIV:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.DIV, fstCode, sndCode)); break;
			case  AbsBinExpr.AND:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.AND, fstCode, sndCode)); break;
			case  AbsBinExpr.OR:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.OR, fstCode, sndCode)); break;
			case  AbsBinExpr.EQU:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.EQU, fstCode, sndCode)); break;
			case  AbsBinExpr.NEQ:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.NEQ, fstCode, sndCode)); break;
			case  AbsBinExpr.GEQ:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.GEQ, fstCode, sndCode)); break;
			case  AbsBinExpr.LEQ:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.LEQ, fstCode, sndCode)); break;
			case  AbsBinExpr.LTH:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.LTH, fstCode, sndCode)); break;
			case  AbsBinExpr.GTH:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.GTH, fstCode, sndCode)); break;
			case AbsBinExpr.ARRACCESS:
				ImcCode array = fstCode;
				//ImcCode field = sndCode;
				ImcExpr arrayAddress = ((ImcMEM)array).expr;
				ImcBINOP lowBound = new ImcBINOP(ImcBINOP.SUB, sndCode, new ImcCONST(this.arrayType.loBound));
				ImcBINOP multiply = new ImcBINOP(ImcBINOP.MUL, lowBound, new ImcCONST(this.arrayType.type.size()));
				ImcBINOP add = new ImcBINOP(ImcBINOP.ADD, arrayAddress, multiply);
				this.imc.put(acceptor, new ImcMEM(add));
				break;
			case AbsBinExpr.RECACCESS:
				ImcCode record = fstCode;
				ImcExpr recordAddress = ((ImcMEM)record).expr;
				ImcExpr offset = sndCode;
				ImcBINOP itemAddress = new ImcBINOP(ImcBINOP.ADD, recordAddress, offset);
				this.imc.put(acceptor, new ImcMEM(itemAddress));
				break;
		}
	}

	@Override
	public void visit(AbsBlockStmt acceptor) {
		acceptor.stmts.accept(this);
		ImcStmt stmt = (ImcStmt) this.imc.get(acceptor.stmts);
		this.imc.put(acceptor, stmt);
	}

	@Override
	public void visit(AbsCallExpr acceptor) {
		AbsDecl subDecl = SemDesc.getNameDecl(acceptor.name);
		FrmFrame calledFrame = FrmDesc.getFrame(subDecl);
		ImcExpr staticLink;
		
		if(calledFrame.level > 0)
			staticLink = returnStaticLink(calledFrame.level-1);
		else
			staticLink = returnStaticLink(0);
		
		ImcCALL call = new ImcCALL(calledFrame.label);
		call.args.add(staticLink);
		call.size.add(4);
		
		for(AbsValExpr arg:acceptor.args.exprs) {
			arg.accept(this);
			call.args.add((ImcExpr) this.imc.get(arg));
			SemType argType = SemDesc.getActualType(arg);
			call.size.add(argType.size());
		}
		this.imc.put(acceptor, call);
	}

	@Override
	public void visit(AbsConstDecl acceptor) {
	}

	@Override
	public void visit(AbsDeclName acceptor) {
	}

	@Override
	public void visit(AbsDecls acceptor) {
		for(AbsDecl decl:acceptor.decls) {
			decl.accept(this);
			
			if(decl instanceof AbsVarDecl && this.level == 0) {
				FrmVarAccess var = (FrmVarAccess)FrmDesc.getAccess(decl);
				SemType varType = SemDesc.getActualType(decl);
				chunks.add(new ImcDataChunk(var.label, varType.size()));
			}
		}
	}

	@Override
	public void visit(AbsExprStmt acceptor) {
		acceptor.expr.accept(this);
		ImcExpr expr = (ImcExpr) this.imc.get(acceptor.expr);
		this.imc.put(acceptor, new ImcEXP(expr));
	}

	@Override
	public void visit(AbsForStmt acceptor) {
		ImcSEQ seq = new ImcSEQ();

		// naredimo izraz i = [lo]
		AbsAssignStmt exp = new AbsAssignStmt(acceptor.name, acceptor.loBound);
		exp.accept(this);
		ImcMOVE assign = (ImcMOVE) this.imc.get(exp);
		seq.stmts.add(assign);

		ImcLABEL start = new ImcLABEL(FrmLabel.newLabel());
		seq.stmts.add(start);

		// naredimo pogoj za skok i<10
		// meje je potrebo racunati vsakic sproti
		acceptor.hiBound.accept(this);
		ImcExpr expr = (ImcExpr) this.imc.get(acceptor.hiBound);
		ImcExpr cond = new ImcBINOP(ImcBINOP.LEQ, assign.dst, expr);
		ImcCJUMP cjump = new ImcCJUMP(cond, FrmLabel.newLabel(), FrmLabel.newLabel());
		seq.stmts.add(cjump);

		ImcLABEL trueLabel = new ImcLABEL(cjump.trueLabel);
		seq.stmts.add(trueLabel);

		// dodamo izraz for stavka
		acceptor.stmt.accept(this);
		seq.stmts.add((ImcStmt) this.imc.get(acceptor.stmt));

		// povecamo stevec
		seq.stmts.add(new ImcMOVE(assign.dst, new ImcBINOP(ImcBINOP.ADD, assign.dst, new ImcCONST(1))));

		// skocimo spet na preverjanje pogoja
		ImcCJUMP jump = new ImcCJUMP(cond, start.label, start.label);
		seq.stmts.add(jump);

		ImcLABEL falseLabel = new ImcLABEL(cjump.falseLabel);
		seq.stmts.add(falseLabel);

		this.imc.put(acceptor, seq);		
	}

	@Override
	public void visit(AbsFunDecl acceptor) {
		FrmFrame frame = FrmDesc.getFrame(acceptor);
		this.level++;
		subprogStack.push(frame);
		acceptor.decls.accept(this);
		
		acceptor.stmt.accept(this);
		ImcSEQ stmt = (ImcSEQ) this.imc.get(acceptor.stmt);
		ImcCodeChunk chunk = new ImcCodeChunk(frame, stmt);
		this.chunks.add(chunk);		
		
		this.level--;
		subprogStack.pop();
	}

	@Override
	public void visit(AbsIfStmt acceptor) {
		ImcSEQ result = new ImcSEQ();
		FrmLabel trueLabel = FrmLabel.newLabel();
		FrmLabel falseLabel = FrmLabel.newLabel();
		FrmLabel endLabel = FrmLabel.newLabel();

		// condition
		acceptor.cond.accept(this);
		ImcExpr cond = (ImcExpr) this.imc.get(acceptor.cond); 
		result.stmts.add(new ImcCJUMP(cond, trueLabel, falseLabel));

		// true label
		result.stmts.add(new ImcLABEL(trueLabel));

		// true stmts
		acceptor.thenStmt.accept(this);
		ImcStmt trueSeq = (ImcStmt) this.imc.get(acceptor.thenStmt);
		result.stmts.add(trueSeq);

		// jump to end
		result.stmts.add(new ImcCJUMP(cond, endLabel, endLabel));

		// false label
		result.stmts.add(new ImcLABEL(falseLabel));

		// false stmts
		acceptor.elseStmt.accept(this);
		ImcStmt falseSeq = (ImcStmt) this.imc.get(acceptor.elseStmt);
		result.stmts.add(falseSeq);

		// end label
		result.stmts.add(new ImcLABEL(endLabel));
		this.imc.put(acceptor, result);	
	}


	@Override
	public void visit(AbsNilConst acceptor) {
		this.imc.put(acceptor, new ImcCONST(0));
	}

	@Override
	public void visit(AbsPointerType acceptor) {
		acceptor.type.accept(this);
	}

	@Override
	public void visit(AbsProcDecl acceptor) {
		FrmFrame frame = FrmDesc.getFrame(acceptor);
		this.level++;
		subprogStack.push(frame);
		acceptor.decls.accept(this);
		
		acceptor.stmt.accept(this);
		ImcSEQ stmt = (ImcSEQ) this.imc.get(acceptor.stmt);
		ImcCodeChunk chunk = new ImcCodeChunk(frame, stmt);
		this.chunks.add(chunk);
		
		this.level--;
		subprogStack.pop();		
	}

	@Override
	public void visit(AbsProgram acceptor) {
		FrmFrame frame = FrmDesc.getFrame(acceptor);
		subprogStack.push(frame);
		
		acceptor.decls.accept(this);
		acceptor.stmt.accept(this);

		ImcSEQ stmt = (ImcSEQ) this.imc.get(acceptor.stmt);
		ImcCodeChunk chunk = new ImcCodeChunk(frame, stmt);
		this.chunks.add(chunk);		
		
		subprogStack.pop();
	}

	@Override
	public void visit(AbsRecordType acceptor) {
		this.record=acceptor;
		
	}

	@Override
	public void visit(AbsStmts acceptor) {
		ImcSEQ seq = new ImcSEQ();
		for(AbsStmt stmt:acceptor.stmts) {
			stmt.accept(this);
			seq.stmts.add((ImcStmt) this.imc.get(stmt));
		}
		this.imc.put(acceptor, seq);
	}

	@Override
	public void visit(AbsTypeDecl acceptor) {
		acceptor.type.accept(this);
	}

	@Override
	public void visit(AbsTypeName acceptor) {
		AbsTypeDecl decl = (AbsTypeDecl) SemDesc.getNameDecl(acceptor);
		decl.accept(this);
	}

	@Override
	public void visit(AbsUnExpr acceptor) {
		acceptor.expr.accept(this);
		ImcExpr expr = (ImcExpr) this.imc.get(acceptor.expr);
		
		switch(acceptor.oper) {
			case AbsUnExpr.ADD:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.ADD, new ImcCONST(0), expr)); break;
			case AbsUnExpr.SUB:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.SUB, new ImcCONST(0), expr)); break;
			case AbsUnExpr.NOT:
				this.imc.put(acceptor, new ImcBINOP(ImcBINOP.EQU, new ImcCONST(0), expr)); break;
			case AbsUnExpr.MEM:
				this.imc.put(acceptor, ((ImcMEM)expr).expr); break;
			case AbsUnExpr.VAL:
				this.imc.put(acceptor, new ImcMEM(expr)); break;
		}
	}

	@Override
	public void visit(AbsValExprs acceptor) {
	}

	@Override
	public void visit(AbsValName acceptor) {
		AbsDecl decl = SemDesc.getNameDecl(acceptor);
		
		if(decl == null)
			decl = this.getRecordFieldDecl(acceptor);
		
		if(!(decl instanceof AbsFunDecl))
			decl.accept(this);
		
		if(decl instanceof AbsConstDecl) {
			AbsConstDecl constDecl = (AbsConstDecl)decl;
			constDecl.value.accept(this);
			ImcExpr value = (ImcExpr) this.imc.get(constDecl.value);
			this.imc.put(acceptor, value);
		}
		else if(decl instanceof AbsFunDecl) {
			FrmFrame frm = FrmDesc.getFrame(decl);
			this.imc.put(acceptor, new ImcTEMP(frm.RV));
		}
		else {
			FrmAccess access = FrmDesc.getAccess(decl);
			
			if(access instanceof FrmVarAccess) {
				ImcMEM instr = new ImcMEM(new ImcNAME(((FrmVarAccess) access).label));
				this.imc.put(acceptor, instr);
			}
			else if(access instanceof FrmLocAccess) {
				FrmLocAccess tempAccess = (FrmLocAccess) access;
				ImcMEM instr = new ImcMEM(new ImcBINOP(ImcBINOP.ADD,
						returnAbsValNameSubTree(tempAccess, this.level), new ImcCONST(tempAccess.offset)));
				this.imc.put(acceptor, instr);
			}
			else if(access instanceof FrmArgAccess) {
				FrmArgAccess tempAccess = (FrmArgAccess) access;
				ImcMEM instr = new ImcMEM(new ImcBINOP(ImcBINOP.ADD,
						returnAbsValNameSubTree(tempAccess, this.level), new ImcCONST(tempAccess.offset)));
				this.imc.put(acceptor, instr);
			}
			else if(access instanceof FrmCmpAccess) {
				FrmCmpAccess tempAccess = (FrmCmpAccess) access;
				this.imc.put(acceptor, new ImcCONST(tempAccess.offset));
			}
		}
	}

	@Override
	public void visit(AbsVarDecl acceptor) {
		acceptor.type.accept(this);
	}

	@Override
	public void visit(AbsWhileStmt acceptor) {
		ImcSEQ result = new ImcSEQ();
		FrmLabel loop = FrmLabel.newLabel(); // loop
		FrmLabel cont = FrmLabel.newLabel(); // continue
		FrmLabel end = FrmLabel.newLabel(); // end
		
		acceptor.cond.accept(this);
		acceptor.stmt.accept(this);
		
		ImcExpr cond = (ImcExpr) this.imc.get(acceptor.cond);
		ImcStmt stmt = (ImcStmt) this.imc.get(acceptor.stmt);

		result.stmts.add(new ImcLABEL(loop));
		result.stmts.add(new ImcCJUMP(cond, cont, end));
		result.stmts.add(new ImcLABEL(cont));
		result.stmts.add(stmt);
		result.stmts.add(new ImcCJUMP(cond, loop, loop));
		result.stmts.add(new ImcLABEL(end));
		
		this.imc.put(acceptor, result);
	}
	
	public ImcExpr returnAbsValNameSubTree(FrmAccess node, int currentLevel) {
		// FP od trenutnega okvirja
		FrmFrame currentFrame = subprogStack.peek();
		
		// ce je argument
		if (node instanceof FrmArgAccess) {
			FrmArgAccess tempAccess = (FrmArgAccess) node;
			
			if (currentLevel == (tempAccess.frame.level+1)) {
				return new ImcTEMP(currentFrame.FP);
			} else {
				return new ImcMEM(returnAbsValNameSubTree(node, currentLevel - 1));
			}
		}
		else {// ce je lokalna
			FrmLocAccess tempAccess = (FrmLocAccess) node;
			
			if (currentLevel == (tempAccess.frame.level+1)) {
				return new ImcTEMP(currentFrame.FP);
			} else {
				return new ImcMEM(returnAbsValNameSubTree(node, currentLevel - 1));
			}
		}	
	}
	
	//gradnja hierarhije ukazov za staticni link pri AbsCallExpr visitorju
	public ImcExpr returnStaticLink(int calledLevel) {
		FrmFrame frmCaller = subprogStack.peek();
		
		if(calledLevel == frmCaller.level)
			return new ImcTEMP(frmCaller.FP);
		else if(calledLevel > frmCaller.level)
			return new ImcMEM(returnStaticLink(calledLevel-1));
		else
			return new ImcMEM(returnStaticLink(calledLevel+1));
	}
	
	private AbsVarDecl getRecordFieldDecl(AbsValName valName) {
		
		for(AbsDecl decl:this.record.fields.decls) {
			AbsVarDecl varDecl = (AbsVarDecl) decl;

			if(varDecl.name.name.equalsIgnoreCase(valName.name))
				return varDecl;
		}
		return null;
	}
}